rm(list=ls(all=TRUE))
library(tidyverse)
library(magrittr)
library(jsonlite)
library(broom)

Read example json file:

results_dir <- "microbeMASST_results/"
json_example <- read_json(str_c(results_dir, "fastMASST_HILIC_neg__165_microbe.json"), simplifyVector = F)

Define function for iterating over nodes in the MASST output:

iterate_masst <- function(masst_node){
  node_attributes <- names(masst_node)
  if ("Rank" %in% node_attributes && masst_node$Rank == "species") {
    tibble(
      NAME = masst_node$name, 
      TYPE = masst_node$type, 
      NCBI = masst_node$NCBI, 
      RANK = masst_node$Rank, 
      GROUP_SIZE = masst_node$group_size, 
      MATCHED_SIZE = masst_node$matched_size
      )
  }
  else {
    if ("type" %in% node_attributes && masst_node$type == "node") {
      lapply(masst_node$children, iterate_masst) %>% 
        bind_rows()
    }
    else {
      tibble(
        NAME = character(), 
        TYPE = character(), 
        NCBI = character(), 
        RANK = character(), 
        GROUP_SIZE = integer(), 
        MATCHED_SIZE = integer()
        )
    }
  }
}

iterate_masst(json_example)

Set up tibble initialized with all json file names:

masst_results <- tibble(FILE_NAME = dir(results_dir, ".*\\.json"))

masst_results

Parse info from file names:

masst_results <- masst_results %>% 
  mutate(
    SEARCH_TYPE = FILE_NAME %>% str_extract("food|microbe"),
    DATASET = FILE_NAME %>% str_extract("(HILIC|RP).+(pos|neg)"),
    DATASET = if_else(DATASET == "RP_neg", "RP18_neg", DATASET),
    SCAN = FILE_NAME %>% str_extract("__.+_") %>% str_extract("[0-9]+"),
    FEATURE_ID = str_c(
      case_when(
        DATASET == "RP18_pos"  ~ "X94",
        DATASET == "RP18_neg"  ~ "X95",
        DATASET == "HILIC_pos" ~ "X96",
        DATASET == "HILIC_neg" ~ "X97"
      ),
      SCAN %>% str_pad(max(nchar(SCAN)), "left", "0")
    )
  )

masst_results

Number of results files per dataset:

masst_results %>% 
  count(DATASET)

Read json files for microbeMASST:

masst_results$JSON[[1]][setdiff(names(masst_results$JSON[[1]]), c("children", "pie_data"))]
$name
[1] "root"

$duplication
[1] "Y"

$type
[1] "node"

$NCBI
[1] "131567"

$Rank
[1] "cellular organisms"

$group_size
[1] 72560

$matched_size
[1] 3

$occurrence_fraction
[1] 4.134509e-05

Add stats for species:

masst_results <- masst_results %>% 
  mutate(
    STATS_ROOT   = JSON %>% map(~ tibble(ROOT_GROUP_SIZE = .$group_size, ROOT_MATCHED_SIZE = .$matched_size)),
    STATS_PHYLUM = JSON %>% map(iterate_masst)
  )

masst_results$STATS_ROOT[[1]]
masst_results$STATS_PHYLUM[[1]]

Select relevant columns and unnest stats:

masst_results <- masst_results %>% 
  select(FEATURE_ID, DATASET, SEARCH_TYPE, STATS_ROOT, STATS_PHYLUM) %>% 
  unnest(c(STATS_ROOT, STATS_PHYLUM))

masst_results

Check: Is there any other TYPE than “node”?

masst_results$TYPE %>% unique()
[1] "leaf" "node"

Check: Is there any other RANK than “species”?

masst_results$RANK %>% unique()
[1] "species"

Perform Fisher’s exact test for the association between features and species:

masst_results <- masst_results %>% 
  filter(MATCHED_SIZE > 0) %>% 
  mutate(
    FISHER = pmap(
      list(
        ROOT_GROUP_SIZE, 
        ROOT_MATCHED_SIZE, 
        GROUP_SIZE, 
        MATCHED_SIZE
      ),
      ~ fisher.test(
        matrix(
          c(..1, ..2, ..3, ..4),
          nrow = 2
        )
      )
    ),
    FISHER = FISHER %>% map(tidy)
  ) %>% 
  unnest(FISHER)

masst_results

Perform correction for multiple testing and check distribution of p-values:

masst_results <- masst_results %>% 
  mutate(p.value.fdr = p.value %>% p.adjust(method = "fdr"))

masst_results %>% 
  ggplot() + 
  geom_point(aes(p.value, p.value.fdr)) +
  geom_abline(slope = 1)


masst_results %>% 
  ggplot() +
  geom_histogram(aes(p.value.fdr, fill = DATASET), bins = 100) +
  scale_x_continuous(breaks = 0:5/5)


masst_results %>% 
  filter(p.value.fdr < 0.1) %>% 
  ggplot() +
  geom_histogram(aes(p.value.fdr, fill = DATASET), bins = 100) +
  scale_x_continuous(breaks = 0:5/50)


masst_results %>% 
    filter(p.value.fdr < 0.01) %>% 
  ggplot() +
  geom_histogram(aes(p.value.fdr, fill = DATASET), bins = 100) +
  scale_x_continuous(breaks = 0:5/500)


masst_results %>% 
  filter(p.value.fdr < 0.001) %>% 
  ggplot() +
  geom_histogram(aes(p.value.fdr, fill = DATASET), bins = 100) +
  scale_x_continuous(breaks = 0:5/5000)

Filter for a p-value < 0.01:

masst_results <- masst_results %>% 
  filter(p.value.fdr < 0.01)

masst_results

Number of significant hits per dataset:

masst_results %>% 
  count(DATASET)

Number of features with significant hits per dataset:

masst_results %>% 
  group_by(DATASET) %>% 
  summarize(N_FEATURES = n_distinct(FEATURE_ID))

Map MASST results from features to families

Read feature annotations from file:

feature_info <- rbind(
  read_tsv("feature_metadata/C18neg_feature_metadata_consolidated_is_microbial.tsv", guess_max = 100000) %>% 
    mutate(MET_CHEM_NO = paste0("X95", formatC(`#featureID`,        width = 5, flag = "0", format = "d"))) %>% 
    mutate(FAMILY_ID   = paste0("X95", formatC(GNPS_componentindex, width = 4, flag = "0", format = "d"))),
  read_tsv("feature_metadata/C18pos_feature_metadata_consolidated_is_microbial.tsv", guess_max = 100000) %>% 
    mutate(MET_CHEM_NO = paste0("X94", formatC(`#featureID`,        width = 5, flag = "0", format = "d"))) %>% 
    mutate(FAMILY_ID   = paste0("X94", formatC(GNPS_componentindex, width = 4, flag = "0", format = "d"))),
  read_tsv("feature_metadata/HILICneg_feature_metadata_consolidated_is_microbial.tsv", guess_max = 100000) %>% 
    mutate(MET_CHEM_NO = paste0("X97", formatC(`#featureID`,        width = 5, flag = "0", format = "d"))) %>% 
    mutate(FAMILY_ID   = paste0("X97", formatC(GNPS_componentindex, width = 4, flag = "0", format = "d"))),
  read_tsv("feature_metadata/HILICpos_feature_metadata_consolidated_is_microbial.tsv", guess_max = 100000) %>% 
    mutate(MET_CHEM_NO = paste0("X96", formatC(`#featureID`,        width = 5, flag = "0", format = "d"))) %>% 
    mutate(FAMILY_ID   = paste0("X96", formatC(GNPS_componentindex, width = 4, flag = "0", format = "d")))
  ) %>% 
  mutate(FAMILY_ID = if_else(str_detect(FAMILY_ID, "-001$"), "Singleton", FAMILY_ID))
Rows: 6155 Columns: 256
-- Column specification ---------------------------------------------------------------------------------------------------------------------------
Delimiter: "\t"
chr (159): GNPS_Best Ion, GNPS_GNPSLinkout_Cluster, GNPS_GNPSLinkout_Network, GNPS_INCHI, GNPS_LibraryID, GNPS_MS2 Verification Comment, GNPS_S...
dbl  (91): #featureID, GNPS_Annotated Adduct Features ID, GNPS_Correlated Features Group ID, GNPS_G1, GNPS_G2, GNPS_G3, GNPS_G4, GNPS_G5, GNPS_...
lgl   (6): GNPS_LIB_Pubmed_ID, GNPS_LIB_INCHI_AUX, GNPS_LIB_tags, GNPS_LIBA_INCHI_AUX, GNPS_LIBA_tags, CSI_ConfidenceScore

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
Rows: 15131 Columns: 256
-- Column specification ---------------------------------------------------------------------------------------------------------------------------
Delimiter: "\t"
chr (158): GNPS_GNPSLinkout_Cluster, GNPS_GNPSLinkout_Network, GNPS_INCHI, GNPS_LibraryID, GNPS_Smiles, GNPS_SpectrumID, GNPS_LIB_SpectrumID, G...
dbl  (90): #featureID, GNPS_G1, GNPS_G2, GNPS_G3, GNPS_G4, GNPS_G5, GNPS_G6, GNPS_MQScore, GNPS_RTConsensus, GNPS_RTMean, GNPS_RTStdErr, GNPS_S...
lgl   (8): GNPS_Annotated Adduct Features ID, GNPS_Best Ion, GNPS_Correlated Features Group ID, GNPS_MS2 Verification Comment, GNPS_neutral M m...

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
Rows: 11230 Columns: 256
-- Column specification ---------------------------------------------------------------------------------------------------------------------------
Delimiter: "\t"
chr (153): GNPS_GNPSLinkout_Cluster, GNPS_GNPSLinkout_Network, GNPS_INCHI, GNPS_LibraryID, GNPS_Smiles, GNPS_SpectrumID, GNPS_LIB_SpectrumID, G...
dbl  (89): #featureID, GNPS_Correlated Features Group ID, GNPS_G1, GNPS_G2, GNPS_G3, GNPS_G4, GNPS_G5, GNPS_G6, GNPS_MQScore, GNPS_RTConsensus,...
lgl  (14): GNPS_Annotated Adduct Features ID, GNPS_Best Ion, GNPS_MS2 Verification Comment, GNPS_neutral M mass, GNPS_LIB_INCHI_AUX, GNPS_LIB_t...

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
Rows: 28762 Columns: 256
-- Column specification ---------------------------------------------------------------------------------------------------------------------------
Delimiter: "\t"
chr (161): GNPS_Best Ion, GNPS_GNPSLinkout_Cluster, GNPS_GNPSLinkout_Network, GNPS_INCHI, GNPS_LibraryID, GNPS_MS2 Verification Comment, GNPS_S...
dbl  (92): #featureID, GNPS_Annotated Adduct Features ID, GNPS_Correlated Features Group ID, GNPS_G1, GNPS_G2, GNPS_G3, GNPS_G4, GNPS_G5, GNPS_...
lgl   (3): GNPS_LIB_INCHI_AUX, GNPS_LIBA_INCHI_AUX, CSI_ConfidenceScore

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.

Add FAMILY_ID to the MASST results:

masst_results <- masst_results %>% 
  inner_join(
    feature_info %>% 
      select(FEATURE_ID = MET_CHEM_NO, FAMILY_ID)
  )
Joining, by = "FEATURE_ID"
masst_results

Number of families with significant hits per dataset:

masst_results %>% 
  group_by(DATASET) %>% 
  summarize(N_FAMILIES = n_distinct(FAMILY_ID), n = n())

Statistical analysis of features

Read statistical results from file:

skin_p_cat_dir <- read_tsv("Untargeted.p_cat_dir.tsv")
Rows: 33333 Columns: 15
-- Column specification ---------------------------------------------------------------------------------------------------------------------------
Delimiter: "\t"
chr (15): MET_CHEM_NO, p_cat_dir|base|sebum, p_cat_dir|base|skicon, p_cat_dir|groups|oily-norm, p_cat_dir|groups|skicon, p_cat_dir|oily|exfol, ...

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
skin_p_value   <- read_tsv("Untargeted.p_value.tsv")
Rows: 44567 Columns: 15
-- Column specification ---------------------------------------------------------------------------------------------------------------------------
Delimiter: "\t"
chr  (1): MET_CHEM_NO
dbl (14): p_value|base|sebum, p_value|base|skicon, p_value|groups|oily-norm, p_value|groups|skicon, p_value|oily|exfol, p_value|oily|F-B, p_val...

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
skin_p_cat_dir %>% colnames()
 [1] "MET_CHEM_NO"                "p_cat_dir|base|sebum"       "p_cat_dir|base|skicon"      "p_cat_dir|groups|oily-norm"
 [5] "p_cat_dir|groups|skicon"    "p_cat_dir|oily|exfol"       "p_cat_dir|oily|F-B"         "p_cat_dir|oily|F-B|A"      
 [9] "p_cat_dir|oily|F-B|B"       "p_cat_dir|oily|F-B|B-A"     "p_cat_dir|oily|F-B|C"       "p_cat_dir|oily|F-B|C-A"    
[13] "p_cat_dir|oily|F-B|C-B"     "p_cat_dir|oily|sebum"       "p_cat_dir|oily|skicon"     
skin_stats <- skin_p_value %>% 
  select(MET_CHEM_NO) %>% 
  left_join(skin_p_cat_dir, by = "MET_CHEM_NO") %>% 
  mutate(
    sebumeter_0.1_any  = `p_cat_dir|base|sebum` %>% is.na(.) %>% not(),
    sebumeter_0.1_up   = `p_cat_dir|base|sebum` %>% is.na(.) %>% not() & `p_cat_dir|base|sebum` %>% str_detect("Up"),
    sebumeter_0.1_down = `p_cat_dir|base|sebum` %>% is.na(.) %>% not() & `p_cat_dir|base|sebum` %>% str_detect("Dn")
  )

skin_stats %>% 
  group_by(`p_cat_dir|base|sebum`, sebumeter_0.1_any) %>% summarize(.groups = "drop")

skin_stats %>% 
  group_by(`p_cat_dir|base|sebum`, sebumeter_0.1_up) %>% summarize(.groups = "drop")

skin_stats %>% 
  group_by(`p_cat_dir|base|sebum`, sebumeter_0.1_down) %>% summarize(.groups = "drop")

Check whether there are skin stats for all features from the MASST results:

masst_results_stats <- masst_results %>% 
  inner_join(
    skin_stats %>% 
      select(FEATURE_ID = MET_CHEM_NO, sebumeter_0.1_any, sebumeter_0.1_up, sebumeter_0.1_down),
    by = "FEATURE_ID"
  )

setdiff(masst_results$FEATURE_ID, skin_stats$MET_CHEM_NO)
character(0)

Bacteria in Fig. 4 (MMvec)

Staphylococcus epidermidis

Are there any masst hits for Staphylococcus epidermidis?

masst_results %>% 
  filter(NAME %>% str_to_lower() %>% str_detect("staph")) %>% 
  select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>% 
  arrange(NAME, FAMILY_ID, FEATURE_ID)

Which of these are correlated with sebumeter score?

masst_results_stats %>% 
  filter(NAME %>% str_to_lower() %>% str_detect("staph") & sebumeter_0.1_any) %>% 
  select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>% 
  arrange(NAME, FAMILY_ID, FEATURE_ID)

Which of these are in one of the families correlated with sebumeter score?

masst_results_stats %>% 
  filter(
    NAME %>% str_to_lower() %>% str_detect("staph") &
    FAMILY_ID %in% c("X940029", "X950190", "X940005", "X950167", "X950477", "X970034")
    ) %>% 
  select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>% 
  arrange(NAME, FAMILY_ID, FEATURE_ID)

Propionibacterium acnes

Are there any masst hits for Propionibacterium acnes?

masst_results %>% 
  filter(NAME %>% str_to_lower() %>% str_detect("propionibac")) %>% 
  select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>% 
  arrange(NAME, FAMILY_ID, FEATURE_ID)

Which of these are correlated with sebumeter score?

masst_results_stats %>% 
  filter(NAME %>% str_to_lower() %>% str_detect("propionibac") & sebumeter_0.1_any) %>% 
  select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>% 
  arrange(NAME, FAMILY_ID, FEATURE_ID)

Which of these are in one of the families correlated with sebumeter score?

masst_results_stats %>% 
  filter(
    NAME %>% str_to_lower() %>% str_detect("propionibac") &
    FAMILY_ID %in% c("X940029", "X950190", "X940005", "X950167", "X950477", "X970034")
    ) %>% 
  select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>% 
  arrange(NAME, FAMILY_ID, FEATURE_ID)

Species in the families correlated with sebumeter score

Family X940029

X940029

Which species are in the family X940029?

masst_results %>% 
  filter(FAMILY_ID == "X940029") %>% 
  select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>% 
  arrange(NAME, FAMILY_ID, FEATURE_ID)

masst_results %>% 
  filter(FAMILY_ID == "X940029") %>% 
  count(NAME)

Which features with MASST hits are in the family X940029?

masst_results %>% 
  filter(FAMILY_ID == "X940029") %>% 
  select(FAMILY_ID, FEATURE_ID, NAME, p.value.fdr) %>% 
  arrange(FAMILY_ID, FEATURE_ID, NAME)

masst_results %>% 
  filter(FAMILY_ID == "X940029") %>% 
  count(FAMILY_ID, FEATURE_ID)

Family X950190

X950190

Which species are in the family X950190?

masst_results %>% 
  filter(FAMILY_ID == "X950190") %>% 
  select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>% 
  arrange(NAME, FAMILY_ID, FEATURE_ID)

Which features with MASST hits are in the family X950190?

masst_results %>% 
  filter(FAMILY_ID == "X950190") %>% 
  select(FAMILY_ID, FEATURE_ID, NAME, p.value.fdr) %>% 
  arrange(FAMILY_ID, FEATURE_ID, NAME)

Family X940005

X940005

Which species are in the family X940005?

masst_results %>% 
  filter(FAMILY_ID == "X940005") %>% 
  select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>% 
  arrange(NAME, FAMILY_ID, FEATURE_ID)

masst_results %>% 
  filter(FAMILY_ID == "X940005") %>% 
  count(NAME)

Which features with MASST hits are in the family X940005?

masst_results %>% 
  filter(FAMILY_ID == "X940005") %>% 
  select(FAMILY_ID, FEATURE_ID, NAME, p.value.fdr) %>% 
  arrange(FAMILY_ID, FEATURE_ID, NAME)

masst_results %>% 
  filter(FAMILY_ID == "X940005") %>% 
  count(FAMILY_ID, FEATURE_ID)

Family X950167

X950167

Which species are in the family X950167?

masst_results %>% 
  filter(FAMILY_ID == "X950167") %>% 
  select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>% 
  arrange(NAME, FAMILY_ID, FEATURE_ID)

masst_results %>% 
  filter(FAMILY_ID == "X950167") %>% 
  count(NAME)

Which features with MASST hits are in the family X950167?

masst_results %>% 
  filter(FAMILY_ID == "X950167") %>% 
  select(FAMILY_ID, FEATURE_ID, NAME, p.value.fdr) %>% 
  arrange(FAMILY_ID, FEATURE_ID, NAME)

masst_results %>% 
  filter(FAMILY_ID == "X950167") %>% 
  count(FAMILY_ID, FEATURE_ID)

Family X950477

X950477

Which species are in the family X950477?

masst_results %>% 
  filter(FAMILY_ID == "X950477") %>% 
  select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>% 
  arrange(NAME, FAMILY_ID, FEATURE_ID)

masst_results %>% 
  filter(FAMILY_ID == "X950477") %>% 
  count(NAME)

Which features with MASST hits are in the family X950477?

masst_results %>% 
  filter(FAMILY_ID == "X950477") %>% 
  select(FAMILY_ID, FEATURE_ID, NAME, p.value.fdr) %>% 
  arrange(FAMILY_ID, FEATURE_ID, NAME)

masst_results %>% 
  filter(FAMILY_ID == "X950477") %>% 
  count(FAMILY_ID, FEATURE_ID)

Family X970034

X970034

Which species are in the family X970034?

masst_results %>% 
  filter(FAMILY_ID == "X970034") %>% 
  select(NAME, FAMILY_ID, FEATURE_ID, p.value.fdr) %>% 
  arrange(NAME, FAMILY_ID, FEATURE_ID)

masst_results %>% 
  filter(FAMILY_ID == "X970034") %>% 
  count(NAME)

Which features with MASST hits are in the family X970034?

masst_results %>% 
  filter(FAMILY_ID == "X970034") %>% 
  select(FAMILY_ID, FEATURE_ID, NAME, p.value.fdr) %>% 
  arrange(FAMILY_ID, FEATURE_ID, NAME)

masst_results %>% 
  filter(FAMILY_ID == "X970034") %>% 
  count(FAMILY_ID, FEATURE_ID)

Session info

sessionInfo()
R version 4.1.2 (2021-11-01)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19044)

Matrix products: default

locale:
[1] LC_COLLATE=English_United States.1252  LC_CTYPE=English_United States.1252    LC_MONETARY=English_United States.1252
[4] LC_NUMERIC=C                           LC_TIME=English_United States.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] broom_0.7.11    jsonlite_1.7.2  magrittr_2.0.1  forcats_0.5.1   stringr_1.4.0   dplyr_1.0.7     purrr_0.3.4     readr_2.1.1    
 [9] tidyr_1.1.4     tibble_3.1.6    ggplot2_3.3.5   tidyverse_1.3.2

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.8          lubridate_1.8.0     assertthat_0.2.1    digest_0.6.29       utf8_1.2.2          R6_2.5.1            cellranger_1.1.0   
 [8] backports_1.4.1     reprex_2.0.1        evaluate_0.19       httr_1.4.2          pillar_1.6.4        rlang_0.4.12        googlesheets4_1.0.0
[15] readxl_1.3.1        rstudioapi_0.13     jquerylib_0.1.4     rmarkdown_2.11      labeling_0.4.2      googledrive_2.0.0   bit_4.0.4          
[22] munsell_0.5.0       tinytex_0.36        compiler_4.1.2      modelr_0.1.8        xfun_0.35           pkgconfig_2.0.3     htmltools_0.5.2    
[29] tidyselect_1.1.1    fansi_0.5.0         crayon_1.4.2        tzdb_0.2.0          dbplyr_2.1.1        withr_2.4.3         grid_4.1.2         
[36] gtable_0.3.0        lifecycle_1.0.1     DBI_1.1.2           scales_1.1.1        cli_3.1.0           stringi_1.7.6       vroom_1.5.7        
[43] farver_2.1.0        fs_1.5.2            xml2_1.3.3          ellipsis_0.3.2      generics_0.1.1      vctrs_0.3.8         tools_4.1.2        
[50] bit64_4.0.5         glue_1.6.0          hms_1.1.1           parallel_4.1.2      fastmap_1.1.0       yaml_2.2.1          colorspace_2.0-2   
[57] gargle_1.2.0        rvest_1.0.2         knitr_1.41          haven_2.4.3        
LS0tDQp0aXRsZTogIm1pY3JvYmVNQVNTVCBTcGVjaWVzIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rDQotLS0NCg0KYGBge3J9DQpybShsaXN0PWxzKGFsbD1UUlVFKSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShtYWdyaXR0cikNCmxpYnJhcnkoanNvbmxpdGUpDQpsaWJyYXJ5KGJyb29tKQ0KYGBgDQoNClJlYWQgZXhhbXBsZSBqc29uIGZpbGU6DQpgYGB7cn0NCnJlc3VsdHNfZGlyIDwtICJtaWNyb2JlTUFTU1RfcmVzdWx0cy8iDQpqc29uX2V4YW1wbGUgPC0gcmVhZF9qc29uKHN0cl9jKHJlc3VsdHNfZGlyLCAiZmFzdE1BU1NUX0hJTElDX25lZ19fMTY1X21pY3JvYmUuanNvbiIpLCBzaW1wbGlmeVZlY3RvciA9IEYpDQpgYGANCg0KRGVmaW5lIGZ1bmN0aW9uIGZvciBpdGVyYXRpbmcgb3ZlciBub2RlcyBpbiB0aGUgTUFTU1Qgb3V0cHV0Og0KYGBge3J9DQppdGVyYXRlX21hc3N0IDwtIGZ1bmN0aW9uKG1hc3N0X25vZGUpew0KICBub2RlX2F0dHJpYnV0ZXMgPC0gbmFtZXMobWFzc3Rfbm9kZSkNCiAgaWYgKCJSYW5rIiAlaW4lIG5vZGVfYXR0cmlidXRlcyAmJiBtYXNzdF9ub2RlJFJhbmsgPT0gInNwZWNpZXMiKSB7DQogICAgdGliYmxlKA0KICAgICAgTkFNRSA9IG1hc3N0X25vZGUkbmFtZSwgDQogICAgICBUWVBFID0gbWFzc3Rfbm9kZSR0eXBlLCANCiAgICAgIE5DQkkgPSBtYXNzdF9ub2RlJE5DQkksIA0KICAgICAgUkFOSyA9IG1hc3N0X25vZGUkUmFuaywgDQogICAgICBHUk9VUF9TSVpFID0gbWFzc3Rfbm9kZSRncm91cF9zaXplLCANCiAgICAgIE1BVENIRURfU0laRSA9IG1hc3N0X25vZGUkbWF0Y2hlZF9zaXplDQogICAgICApDQogIH0NCiAgZWxzZSB7DQogICAgaWYgKCJ0eXBlIiAlaW4lIG5vZGVfYXR0cmlidXRlcyAmJiBtYXNzdF9ub2RlJHR5cGUgPT0gIm5vZGUiKSB7DQogICAgICBsYXBwbHkobWFzc3Rfbm9kZSRjaGlsZHJlbiwgaXRlcmF0ZV9tYXNzdCkgJT4lIA0KICAgICAgICBiaW5kX3Jvd3MoKQ0KICAgIH0NCiAgICBlbHNlIHsNCiAgICAgIHRpYmJsZSgNCiAgICAgICAgTkFNRSA9IGNoYXJhY3RlcigpLCANCiAgICAgICAgVFlQRSA9IGNoYXJhY3RlcigpLCANCiAgICAgICAgTkNCSSA9IGNoYXJhY3RlcigpLCANCiAgICAgICAgUkFOSyA9IGNoYXJhY3RlcigpLCANCiAgICAgICAgR1JPVVBfU0laRSA9IGludGVnZXIoKSwgDQogICAgICAgIE1BVENIRURfU0laRSA9IGludGVnZXIoKQ0KICAgICAgICApDQogICAgfQ0KICB9DQp9DQoNCml0ZXJhdGVfbWFzc3QoanNvbl9leGFtcGxlKQ0KYGBgDQoNClNldCB1cCB0aWJibGUgaW5pdGlhbGl6ZWQgd2l0aCBhbGwganNvbiBmaWxlIG5hbWVzOg0KYGBge3J9DQptYXNzdF9yZXN1bHRzIDwtIHRpYmJsZShGSUxFX05BTUUgPSBkaXIocmVzdWx0c19kaXIsICIuKlxcLmpzb24iKSkNCg0KbWFzc3RfcmVzdWx0cw0KYGBgDQoNClBhcnNlIGluZm8gZnJvbSBmaWxlIG5hbWVzOg0KYGBge3J9DQptYXNzdF9yZXN1bHRzIDwtIG1hc3N0X3Jlc3VsdHMgJT4lIA0KICBtdXRhdGUoDQogICAgU0VBUkNIX1RZUEUgPSBGSUxFX05BTUUgJT4lIHN0cl9leHRyYWN0KCJmb29kfG1pY3JvYmUiKSwNCiAgICBEQVRBU0VUID0gRklMRV9OQU1FICU+JSBzdHJfZXh0cmFjdCgiKEhJTElDfFJQKS4rKHBvc3xuZWcpIiksDQogICAgREFUQVNFVCA9IGlmX2Vsc2UoREFUQVNFVCA9PSAiUlBfbmVnIiwgIlJQMThfbmVnIiwgREFUQVNFVCksDQogICAgU0NBTiA9IEZJTEVfTkFNRSAlPiUgc3RyX2V4dHJhY3QoIl9fLitfIikgJT4lIHN0cl9leHRyYWN0KCJbMC05XSsiKSwNCiAgICBGRUFUVVJFX0lEID0gc3RyX2MoDQogICAgICBjYXNlX3doZW4oDQogICAgICAgIERBVEFTRVQgPT0gIlJQMThfcG9zIiAgfiAiWDk0IiwNCiAgICAgICAgREFUQVNFVCA9PSAiUlAxOF9uZWciICB+ICJYOTUiLA0KICAgICAgICBEQVRBU0VUID09ICJISUxJQ19wb3MiIH4gIlg5NiIsDQogICAgICAgIERBVEFTRVQgPT0gIkhJTElDX25lZyIgfiAiWDk3Ig0KICAgICAgKSwNCiAgICAgIFNDQU4gJT4lIHN0cl9wYWQobWF4KG5jaGFyKFNDQU4pKSwgImxlZnQiLCAiMCIpDQogICAgKQ0KICApDQoNCm1hc3N0X3Jlc3VsdHMNCmBgYA0KDQpOdW1iZXIgb2YgcmVzdWx0cyBmaWxlcyBwZXIgZGF0YXNldDoNCmBgYHtyfQ0KbWFzc3RfcmVzdWx0cyAlPiUgDQogIGNvdW50KERBVEFTRVQpDQpgYGANCg0KUmVhZCBqc29uIGZpbGVzIGZvciBtaWNyb2JlTUFTU1Q6DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHMgPC0gbWFzc3RfcmVzdWx0cyAlPiUgDQogIG11dGF0ZSgNCiAgICBQQVRIID0gc3RyX2MocmVzdWx0c19kaXIsIEZJTEVfTkFNRSksDQogICAgSlNPTiA9IFBBVEggJT4lIG1hcChyZWFkX2pzb24pLA0KICAgIFNUQVRTX1BIWUxVTSA9IEpTT04gJT4lIG1hcChpdGVyYXRlX21hc3N0KQ0KICApDQoNCm1hc3N0X3Jlc3VsdHMkSlNPTltbMV1dW3NldGRpZmYobmFtZXMobWFzc3RfcmVzdWx0cyRKU09OW1sxXV0pLCBjKCJjaGlsZHJlbiIsICJwaWVfZGF0YSIpKV0NCmBgYA0KDQpBZGQgc3RhdHMgZm9yIHNwZWNpZXM6DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHMgPC0gbWFzc3RfcmVzdWx0cyAlPiUgDQogIG11dGF0ZSgNCiAgICBTVEFUU19ST09UICAgPSBKU09OICU+JSBtYXAofiB0aWJibGUoUk9PVF9HUk9VUF9TSVpFID0gLiRncm91cF9zaXplLCBST09UX01BVENIRURfU0laRSA9IC4kbWF0Y2hlZF9zaXplKSksDQogICAgU1RBVFNfUEhZTFVNID0gSlNPTiAlPiUgbWFwKGl0ZXJhdGVfbWFzc3QpDQogICkNCg0KbWFzc3RfcmVzdWx0cyRTVEFUU19ST09UW1sxXV0NCm1hc3N0X3Jlc3VsdHMkU1RBVFNfUEhZTFVNW1sxXV0NCmBgYA0KDQpTZWxlY3QgcmVsZXZhbnQgY29sdW1ucyBhbmQgdW5uZXN0IHN0YXRzOg0KYGBge3J9DQptYXNzdF9yZXN1bHRzIDwtIG1hc3N0X3Jlc3VsdHMgJT4lIA0KICBzZWxlY3QoRkVBVFVSRV9JRCwgREFUQVNFVCwgU0VBUkNIX1RZUEUsIFNUQVRTX1JPT1QsIFNUQVRTX1BIWUxVTSkgJT4lIA0KICB1bm5lc3QoYyhTVEFUU19ST09ULCBTVEFUU19QSFlMVU0pKQ0KDQptYXNzdF9yZXN1bHRzDQpgYGANCg0KQ2hlY2s6IElzIHRoZXJlIGFueSBvdGhlciBgVFlQRWAgdGhhbiAibm9kZSI/DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHMkVFlQRSAlPiUgdW5pcXVlKCkNCmBgYA0KDQpDaGVjazogSXMgdGhlcmUgYW55IG90aGVyIGBSQU5LYCB0aGFuICJzcGVjaWVzIj8NCmBgYHtyfQ0KbWFzc3RfcmVzdWx0cyRSQU5LICU+JSB1bmlxdWUoKQ0KYGBgDQoNClBlcmZvcm0gRmlzaGVyJ3MgZXhhY3QgdGVzdCBmb3IgdGhlIGFzc29jaWF0aW9uIGJldHdlZW4gZmVhdHVyZXMgYW5kIHNwZWNpZXM6DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHMgPC0gbWFzc3RfcmVzdWx0cyAlPiUgDQogIGZpbHRlcihNQVRDSEVEX1NJWkUgPiAwKSAlPiUgDQogIG11dGF0ZSgNCiAgICBGSVNIRVIgPSBwbWFwKA0KICAgICAgbGlzdCgNCiAgICAgICAgUk9PVF9HUk9VUF9TSVpFLCANCiAgICAgICAgUk9PVF9NQVRDSEVEX1NJWkUsIA0KICAgICAgICBHUk9VUF9TSVpFLCANCiAgICAgICAgTUFUQ0hFRF9TSVpFDQogICAgICApLA0KICAgICAgfiBmaXNoZXIudGVzdCgNCiAgICAgICAgbWF0cml4KA0KICAgICAgICAgIGMoLi4xLCAuLjIsIC4uMywgLi40KSwNCiAgICAgICAgICBucm93ID0gMg0KICAgICAgICApDQogICAgICApDQogICAgKSwNCiAgICBGSVNIRVIgPSBGSVNIRVIgJT4lIG1hcCh0aWR5KQ0KICApICU+JSANCiAgdW5uZXN0KEZJU0hFUikNCg0KbWFzc3RfcmVzdWx0cw0KYGBgDQoNClBlcmZvcm0gY29ycmVjdGlvbiBmb3IgbXVsdGlwbGUgdGVzdGluZyBhbmQgY2hlY2sgZGlzdHJpYnV0aW9uIG9mIHAtdmFsdWVzOg0KYGBge3J9DQptYXNzdF9yZXN1bHRzIDwtIG1hc3N0X3Jlc3VsdHMgJT4lIA0KICBtdXRhdGUocC52YWx1ZS5mZHIgPSBwLnZhbHVlICU+JSBwLmFkanVzdChtZXRob2QgPSAiZmRyIikpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBnZ3Bsb3QoKSArIA0KICBnZW9tX3BvaW50KGFlcyhwLnZhbHVlLCBwLnZhbHVlLmZkcikpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxKQ0KDQptYXNzdF9yZXN1bHRzICU+JSANCiAgZ2dwbG90KCkgKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMocC52YWx1ZS5mZHIsIGZpbGwgPSBEQVRBU0VUKSwgYmlucyA9IDEwMCkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gMDo1LzUpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIocC52YWx1ZS5mZHIgPCAwLjEpICU+JSANCiAgZ2dwbG90KCkgKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMocC52YWx1ZS5mZHIsIGZpbGwgPSBEQVRBU0VUKSwgYmlucyA9IDEwMCkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gMDo1LzUwKQ0KDQptYXNzdF9yZXN1bHRzICU+JSANCiAgICBmaWx0ZXIocC52YWx1ZS5mZHIgPCAwLjAxKSAlPiUgDQogIGdncGxvdCgpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHAudmFsdWUuZmRyLCBmaWxsID0gREFUQVNFVCksIGJpbnMgPSAxMDApICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IDA6NS81MDApDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIocC52YWx1ZS5mZHIgPCAwLjAwMSkgJT4lIA0KICBnZ3Bsb3QoKSArDQogIGdlb21faGlzdG9ncmFtKGFlcyhwLnZhbHVlLmZkciwgZmlsbCA9IERBVEFTRVQpLCBiaW5zID0gMTAwKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSAwOjUvNTAwMCkNCmBgYA0KDQpGaWx0ZXIgZm9yIGEgcC12YWx1ZSA8IDAuMDE6DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHMgPC0gbWFzc3RfcmVzdWx0cyAlPiUgDQogIGZpbHRlcihwLnZhbHVlLmZkciA8IDAuMDEpDQoNCm1hc3N0X3Jlc3VsdHMNCmBgYA0KDQpOdW1iZXIgb2Ygc2lnbmlmaWNhbnQgaGl0cyBwZXIgZGF0YXNldDoNCmBgYHtyfQ0KbWFzc3RfcmVzdWx0cyAlPiUgDQogIGNvdW50KERBVEFTRVQpDQpgYGANCg0KTnVtYmVyIG9mIGZlYXR1cmVzIHdpdGggc2lnbmlmaWNhbnQgaGl0cyBwZXIgZGF0YXNldDoNCmBgYHtyfQ0KbWFzc3RfcmVzdWx0cyAlPiUgDQogIGdyb3VwX2J5KERBVEFTRVQpICU+JSANCiAgc3VtbWFyaXplKE5fRkVBVFVSRVMgPSBuX2Rpc3RpbmN0KEZFQVRVUkVfSUQpKQ0KYGBgDQoNCiMgTWFwIE1BU1NUIHJlc3VsdHMgZnJvbSBmZWF0dXJlcyB0byBmYW1pbGllcw0KDQpSZWFkIGZlYXR1cmUgYW5ub3RhdGlvbnMgZnJvbSBmaWxlOg0KYGBge3J9DQpmZWF0dXJlX2luZm8gPC0gcmJpbmQoDQogIHJlYWRfdHN2KCJmZWF0dXJlX21ldGFkYXRhL0MxOG5lZ19mZWF0dXJlX21ldGFkYXRhX2NvbnNvbGlkYXRlZF9pc19taWNyb2JpYWwudHN2IiwgZ3Vlc3NfbWF4ID0gMTAwMDAwKSAlPiUgDQogICAgbXV0YXRlKE1FVF9DSEVNX05PID0gcGFzdGUwKCJYOTUiLCBmb3JtYXRDKGAjZmVhdHVyZUlEYCwgICAgICAgIHdpZHRoID0gNSwgZmxhZyA9ICIwIiwgZm9ybWF0ID0gImQiKSkpICU+JSANCiAgICBtdXRhdGUoRkFNSUxZX0lEICAgPSBwYXN0ZTAoIlg5NSIsIGZvcm1hdEMoR05QU19jb21wb25lbnRpbmRleCwgd2lkdGggPSA0LCBmbGFnID0gIjAiLCBmb3JtYXQgPSAiZCIpKSksDQogIHJlYWRfdHN2KCJmZWF0dXJlX21ldGFkYXRhL0MxOHBvc19mZWF0dXJlX21ldGFkYXRhX2NvbnNvbGlkYXRlZF9pc19taWNyb2JpYWwudHN2IiwgZ3Vlc3NfbWF4ID0gMTAwMDAwKSAlPiUgDQogICAgbXV0YXRlKE1FVF9DSEVNX05PID0gcGFzdGUwKCJYOTQiLCBmb3JtYXRDKGAjZmVhdHVyZUlEYCwgICAgICAgIHdpZHRoID0gNSwgZmxhZyA9ICIwIiwgZm9ybWF0ID0gImQiKSkpICU+JSANCiAgICBtdXRhdGUoRkFNSUxZX0lEICAgPSBwYXN0ZTAoIlg5NCIsIGZvcm1hdEMoR05QU19jb21wb25lbnRpbmRleCwgd2lkdGggPSA0LCBmbGFnID0gIjAiLCBmb3JtYXQgPSAiZCIpKSksDQogIHJlYWRfdHN2KCJmZWF0dXJlX21ldGFkYXRhL0hJTElDbmVnX2ZlYXR1cmVfbWV0YWRhdGFfY29uc29saWRhdGVkX2lzX21pY3JvYmlhbC50c3YiLCBndWVzc19tYXggPSAxMDAwMDApICU+JSANCiAgICBtdXRhdGUoTUVUX0NIRU1fTk8gPSBwYXN0ZTAoIlg5NyIsIGZvcm1hdEMoYCNmZWF0dXJlSURgLCAgICAgICAgd2lkdGggPSA1LCBmbGFnID0gIjAiLCBmb3JtYXQgPSAiZCIpKSkgJT4lIA0KICAgIG11dGF0ZShGQU1JTFlfSUQgICA9IHBhc3RlMCgiWDk3IiwgZm9ybWF0QyhHTlBTX2NvbXBvbmVudGluZGV4LCB3aWR0aCA9IDQsIGZsYWcgPSAiMCIsIGZvcm1hdCA9ICJkIikpKSwNCiAgcmVhZF90c3YoImZlYXR1cmVfbWV0YWRhdGEvSElMSUNwb3NfZmVhdHVyZV9tZXRhZGF0YV9jb25zb2xpZGF0ZWRfaXNfbWljcm9iaWFsLnRzdiIsIGd1ZXNzX21heCA9IDEwMDAwMCkgJT4lIA0KICAgIG11dGF0ZShNRVRfQ0hFTV9OTyA9IHBhc3RlMCgiWDk2IiwgZm9ybWF0QyhgI2ZlYXR1cmVJRGAsICAgICAgICB3aWR0aCA9IDUsIGZsYWcgPSAiMCIsIGZvcm1hdCA9ICJkIikpKSAlPiUgDQogICAgbXV0YXRlKEZBTUlMWV9JRCAgID0gcGFzdGUwKCJYOTYiLCBmb3JtYXRDKEdOUFNfY29tcG9uZW50aW5kZXgsIHdpZHRoID0gNCwgZmxhZyA9ICIwIiwgZm9ybWF0ID0gImQiKSkpDQogICkgJT4lIA0KICBtdXRhdGUoRkFNSUxZX0lEID0gaWZfZWxzZShzdHJfZGV0ZWN0KEZBTUlMWV9JRCwgIi0wMDEkIiksICJTaW5nbGV0b24iLCBGQU1JTFlfSUQpKQ0KYGBgDQoNCkFkZCBGQU1JTFlfSUQgdG8gdGhlIE1BU1NUIHJlc3VsdHM6DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHMgPC0gbWFzc3RfcmVzdWx0cyAlPiUgDQogIGlubmVyX2pvaW4oDQogICAgZmVhdHVyZV9pbmZvICU+JSANCiAgICAgIHNlbGVjdChGRUFUVVJFX0lEID0gTUVUX0NIRU1fTk8sIEZBTUlMWV9JRCkNCiAgKQ0KDQptYXNzdF9yZXN1bHRzDQpgYGANCg0KTnVtYmVyIG9mIGZhbWlsaWVzIHdpdGggc2lnbmlmaWNhbnQgaGl0cyBwZXIgZGF0YXNldDoNCmBgYHtyfQ0KbWFzc3RfcmVzdWx0cyAlPiUgDQogIGdyb3VwX2J5KERBVEFTRVQpICU+JSANCiAgc3VtbWFyaXplKE5fRkFNSUxJRVMgPSBuX2Rpc3RpbmN0KEZBTUlMWV9JRCksIG4gPSBuKCkpDQpgYGANCg0KIyBTdGF0aXN0aWNhbCBhbmFseXNpcyBvZiBmZWF0dXJlcw0KDQpSZWFkIHN0YXRpc3RpY2FsIHJlc3VsdHMgZnJvbSBmaWxlOg0KYGBge3J9DQpza2luX3BfY2F0X2RpciA8LSByZWFkX3RzdigiVW50YXJnZXRlZC5wX2NhdF9kaXIudHN2IikNCnNraW5fcF92YWx1ZSAgIDwtIHJlYWRfdHN2KCJVbnRhcmdldGVkLnBfdmFsdWUudHN2IikNCmBgYA0KDQpgYGB7cn0NCnNraW5fcF9jYXRfZGlyICU+JSBjb2xuYW1lcygpDQpgYGANCg0KDQpgYGB7cn0NCnNraW5fc3RhdHMgPC0gc2tpbl9wX3ZhbHVlICU+JSANCiAgc2VsZWN0KE1FVF9DSEVNX05PKSAlPiUgDQogIGxlZnRfam9pbihza2luX3BfY2F0X2RpciwgYnkgPSAiTUVUX0NIRU1fTk8iKSAlPiUgDQogIG11dGF0ZSgNCiAgICBzZWJ1bWV0ZXJfMC4xX2FueSAgPSBgcF9jYXRfZGlyfGJhc2V8c2VidW1gICU+JSBpcy5uYSguKSAlPiUgbm90KCksDQogICAgc2VidW1ldGVyXzAuMV91cCAgID0gYHBfY2F0X2RpcnxiYXNlfHNlYnVtYCAlPiUgaXMubmEoLikgJT4lIG5vdCgpICYgYHBfY2F0X2RpcnxiYXNlfHNlYnVtYCAlPiUgc3RyX2RldGVjdCgiVXAiKSwNCiAgICBzZWJ1bWV0ZXJfMC4xX2Rvd24gPSBgcF9jYXRfZGlyfGJhc2V8c2VidW1gICU+JSBpcy5uYSguKSAlPiUgbm90KCkgJiBgcF9jYXRfZGlyfGJhc2V8c2VidW1gICU+JSBzdHJfZGV0ZWN0KCJEbiIpDQogICkNCg0Kc2tpbl9zdGF0cyAlPiUgDQogIGdyb3VwX2J5KGBwX2NhdF9kaXJ8YmFzZXxzZWJ1bWAsIHNlYnVtZXRlcl8wLjFfYW55KSAlPiUgc3VtbWFyaXplKC5ncm91cHMgPSAiZHJvcCIpDQoNCnNraW5fc3RhdHMgJT4lIA0KICBncm91cF9ieShgcF9jYXRfZGlyfGJhc2V8c2VidW1gLCBzZWJ1bWV0ZXJfMC4xX3VwKSAlPiUgc3VtbWFyaXplKC5ncm91cHMgPSAiZHJvcCIpDQoNCnNraW5fc3RhdHMgJT4lIA0KICBncm91cF9ieShgcF9jYXRfZGlyfGJhc2V8c2VidW1gLCBzZWJ1bWV0ZXJfMC4xX2Rvd24pICU+JSBzdW1tYXJpemUoLmdyb3VwcyA9ICJkcm9wIikNCmBgYA0KDQpDaGVjayB3aGV0aGVyIHRoZXJlIGFyZSBza2luIHN0YXRzIGZvciBhbGwgZmVhdHVyZXMgZnJvbSB0aGUgTUFTU1QgcmVzdWx0czoNCmBgYHtyfQ0KbWFzc3RfcmVzdWx0c19zdGF0cyA8LSBtYXNzdF9yZXN1bHRzICU+JSANCiAgaW5uZXJfam9pbigNCiAgICBza2luX3N0YXRzICU+JSANCiAgICAgIHNlbGVjdChGRUFUVVJFX0lEID0gTUVUX0NIRU1fTk8sIHNlYnVtZXRlcl8wLjFfYW55LCBzZWJ1bWV0ZXJfMC4xX3VwLCBzZWJ1bWV0ZXJfMC4xX2Rvd24pLA0KICAgIGJ5ID0gIkZFQVRVUkVfSUQiDQogICkNCg0Kc2V0ZGlmZihtYXNzdF9yZXN1bHRzJEZFQVRVUkVfSUQsIHNraW5fc3RhdHMkTUVUX0NIRU1fTk8pDQpgYGANCg0KKiBZZXMNCg0KIyBCYWN0ZXJpYSBpbiBGaWcuIDQgKE1NdmVjKQ0KIyMgU3RhcGh5bG9jb2NjdXMgZXBpZGVybWlkaXMNCg0KQXJlIHRoZXJlIGFueSBtYXNzdCBoaXRzIGZvciBTdGFwaHlsb2NvY2N1cyBlcGlkZXJtaWRpcz8NCmBgYHtyfQ0KbWFzc3RfcmVzdWx0cyAlPiUgDQogIGZpbHRlcihOQU1FICU+JSBzdHJfdG9fbG93ZXIoKSAlPiUgc3RyX2RldGVjdCgic3RhcGgiKSkgJT4lIA0KICBzZWxlY3QoTkFNRSwgRkFNSUxZX0lELCBGRUFUVVJFX0lELCBwLnZhbHVlLmZkcikgJT4lIA0KICBhcnJhbmdlKE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCkNCmBgYA0KDQpXaGljaCBvZiB0aGVzZSBhcmUgY29ycmVsYXRlZCB3aXRoIHNlYnVtZXRlciBzY29yZT8NCmBgYHtyfQ0KbWFzc3RfcmVzdWx0c19zdGF0cyAlPiUgDQogIGZpbHRlcihOQU1FICU+JSBzdHJfdG9fbG93ZXIoKSAlPiUgc3RyX2RldGVjdCgic3RhcGgiKSAmIHNlYnVtZXRlcl8wLjFfYW55KSAlPiUgDQogIHNlbGVjdChOQU1FLCBGQU1JTFlfSUQsIEZFQVRVUkVfSUQsIHAudmFsdWUuZmRyKSAlPiUgDQogIGFycmFuZ2UoTkFNRSwgRkFNSUxZX0lELCBGRUFUVVJFX0lEKQ0KYGBgDQoNCldoaWNoIG9mIHRoZXNlIGFyZSBpbiBvbmUgb2YgdGhlIGZhbWlsaWVzIGNvcnJlbGF0ZWQgd2l0aCBzZWJ1bWV0ZXIgc2NvcmU/DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHNfc3RhdHMgJT4lIA0KICBmaWx0ZXIoDQogICAgTkFNRSAlPiUgc3RyX3RvX2xvd2VyKCkgJT4lIHN0cl9kZXRlY3QoInN0YXBoIikgJg0KICAgIEZBTUlMWV9JRCAlaW4lIGMoIlg5NDAwMjkiLCAiWDk1MDE5MCIsICJYOTQwMDA1IiwgIlg5NTAxNjciLCAiWDk1MDQ3NyIsICJYOTcwMDM0IikNCiAgICApICU+JSANCiAgc2VsZWN0KE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShOQU1FLCBGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQpgYGANCg0KIyMgUHJvcGlvbmliYWN0ZXJpdW0gYWNuZXMNCg0KQXJlIHRoZXJlIGFueSBtYXNzdCBoaXRzIGZvciBQcm9waW9uaWJhY3Rlcml1bSBhY25lcz8NCmBgYHtyfQ0KbWFzc3RfcmVzdWx0cyAlPiUgDQogIGZpbHRlcihOQU1FICU+JSBzdHJfdG9fbG93ZXIoKSAlPiUgc3RyX2RldGVjdCgicHJvcGlvbmliYWMiKSkgJT4lIA0KICBzZWxlY3QoTkFNRSwgRkFNSUxZX0lELCBGRUFUVVJFX0lELCBwLnZhbHVlLmZkcikgJT4lIA0KICBhcnJhbmdlKE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCkNCmBgYA0KDQpXaGljaCBvZiB0aGVzZSBhcmUgY29ycmVsYXRlZCB3aXRoIHNlYnVtZXRlciBzY29yZT8NCmBgYHtyfQ0KbWFzc3RfcmVzdWx0c19zdGF0cyAlPiUgDQogIGZpbHRlcihOQU1FICU+JSBzdHJfdG9fbG93ZXIoKSAlPiUgc3RyX2RldGVjdCgicHJvcGlvbmliYWMiKSAmIHNlYnVtZXRlcl8wLjFfYW55KSAlPiUgDQogIHNlbGVjdChOQU1FLCBGQU1JTFlfSUQsIEZFQVRVUkVfSUQsIHAudmFsdWUuZmRyKSAlPiUgDQogIGFycmFuZ2UoTkFNRSwgRkFNSUxZX0lELCBGRUFUVVJFX0lEKQ0KYGBgDQoNCldoaWNoIG9mIHRoZXNlIGFyZSBpbiBvbmUgb2YgdGhlIGZhbWlsaWVzIGNvcnJlbGF0ZWQgd2l0aCBzZWJ1bWV0ZXIgc2NvcmU/DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHNfc3RhdHMgJT4lIA0KICBmaWx0ZXIoDQogICAgTkFNRSAlPiUgc3RyX3RvX2xvd2VyKCkgJT4lIHN0cl9kZXRlY3QoInByb3Bpb25pYmFjIikgJg0KICAgIEZBTUlMWV9JRCAlaW4lIGMoIlg5NDAwMjkiLCAiWDk1MDE5MCIsICJYOTQwMDA1IiwgIlg5NTAxNjciLCAiWDk1MDQ3NyIsICJYOTcwMDM0IikNCiAgICApICU+JSANCiAgc2VsZWN0KE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShOQU1FLCBGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQpgYGANCg0KIyBTcGVjaWVzIGluIHRoZSBmYW1pbGllcyBjb3JyZWxhdGVkIHdpdGggc2VidW1ldGVyIHNjb3JlDQojIyBGYW1pbHkgWDk0MDAyOQ0KDQohW1g5NDAwMjldKDk0MDAyOSBGYXR0eSBhY2lkIG1ldGh5bCBvciBldGh5bCBlc3RlcnMucG5nKQ0KDQpXaGljaCBzcGVjaWVzIGFyZSBpbiB0aGUgZmFtaWx5IFg5NDAwMjk/DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTQwMDI5IikgJT4lIA0KICBzZWxlY3QoTkFNRSwgRkFNSUxZX0lELCBGRUFUVVJFX0lELCBwLnZhbHVlLmZkcikgJT4lIA0KICBhcnJhbmdlKE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCkNCg0KbWFzc3RfcmVzdWx0cyAlPiUgDQogIGZpbHRlcihGQU1JTFlfSUQgPT0gIlg5NDAwMjkiKSAlPiUgDQogIGNvdW50KE5BTUUpDQpgYGANCg0KV2hpY2ggZmVhdHVyZXMgd2l0aCBNQVNTVCBoaXRzIGFyZSBpbiB0aGUgZmFtaWx5IFg5NDAwMjk/DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTQwMDI5IikgJT4lIA0KICBzZWxlY3QoRkFNSUxZX0lELCBGRUFUVVJFX0lELCBOQU1FLCBwLnZhbHVlLmZkcikgJT4lIA0KICBhcnJhbmdlKEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgTkFNRSkNCg0KbWFzc3RfcmVzdWx0cyAlPiUgDQogIGZpbHRlcihGQU1JTFlfSUQgPT0gIlg5NDAwMjkiKSAlPiUgDQogIGNvdW50KEZBTUlMWV9JRCwgRkVBVFVSRV9JRCkNCmBgYA0KDQojIyBGYW1pbHkgWDk1MDE5MA0KDQohW1g5NTAxOTBdKFg5NTAxOTAucG5nKQ0KDQpXaGljaCBzcGVjaWVzIGFyZSBpbiB0aGUgZmFtaWx5IFg5NTAxOTA/DQpgYGB7cn0NCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTUwMTkwIikgJT4lIA0KICBzZWxlY3QoTkFNRSwgRkFNSUxZX0lELCBGRUFUVVJFX0lELCBwLnZhbHVlLmZkcikgJT4lIA0KICBhcnJhbmdlKE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCkNCmBgYA0KDQpXaGljaCBmZWF0dXJlcyB3aXRoIE1BU1NUIGhpdHMgYXJlIGluIHRoZSBmYW1pbHkgWDk1MDE5MD8NCmBgYHtyfQ0KbWFzc3RfcmVzdWx0cyAlPiUgDQogIGZpbHRlcihGQU1JTFlfSUQgPT0gIlg5NTAxOTAiKSAlPiUgDQogIHNlbGVjdChGQU1JTFlfSUQsIEZFQVRVUkVfSUQsIE5BTUUsIHAudmFsdWUuZmRyKSAlPiUgDQogIGFycmFuZ2UoRkFNSUxZX0lELCBGRUFUVVJFX0lELCBOQU1FKQ0KYGBgDQoNCiMjIEZhbWlseSBYOTQwMDA1DQoNCiFbWDk0MDAwNV0oOTQwMDA1LnBuZykNCg0KV2hpY2ggc3BlY2llcyBhcmUgaW4gdGhlIGZhbWlseSBYOTQwMDA1Pw0KYGBge3J9DQptYXNzdF9yZXN1bHRzICU+JSANCiAgZmlsdGVyKEZBTUlMWV9JRCA9PSAiWDk0MDAwNSIpICU+JSANCiAgc2VsZWN0KE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShOQU1FLCBGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTQwMDA1IikgJT4lIA0KICBjb3VudChOQU1FKQ0KYGBgDQoNCldoaWNoIGZlYXR1cmVzIHdpdGggTUFTU1QgaGl0cyBhcmUgaW4gdGhlIGZhbWlseSBYOTQwMDA1Pw0KYGBge3J9DQptYXNzdF9yZXN1bHRzICU+JSANCiAgZmlsdGVyKEZBTUlMWV9JRCA9PSAiWDk0MDAwNSIpICU+JSANCiAgc2VsZWN0KEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgTkFNRSwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShGQU1JTFlfSUQsIEZFQVRVUkVfSUQsIE5BTUUpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTQwMDA1IikgJT4lIA0KICBjb3VudChGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQpgYGANCg0KIyMgRmFtaWx5IFg5NTAxNjcNCg0KIVtYOTUwMTY3XShYOTUwMTY3LnBuZykNCg0KV2hpY2ggc3BlY2llcyBhcmUgaW4gdGhlIGZhbWlseSBYOTUwMTY3Pw0KYGBge3J9DQptYXNzdF9yZXN1bHRzICU+JSANCiAgZmlsdGVyKEZBTUlMWV9JRCA9PSAiWDk1MDE2NyIpICU+JSANCiAgc2VsZWN0KE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShOQU1FLCBGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTUwMTY3IikgJT4lIA0KICBjb3VudChOQU1FKQ0KYGBgDQoNCldoaWNoIGZlYXR1cmVzIHdpdGggTUFTU1QgaGl0cyBhcmUgaW4gdGhlIGZhbWlseSBYOTUwMTY3Pw0KYGBge3J9DQptYXNzdF9yZXN1bHRzICU+JSANCiAgZmlsdGVyKEZBTUlMWV9JRCA9PSAiWDk1MDE2NyIpICU+JSANCiAgc2VsZWN0KEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgTkFNRSwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShGQU1JTFlfSUQsIEZFQVRVUkVfSUQsIE5BTUUpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTUwMTY3IikgJT4lIA0KICBjb3VudChGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQpgYGANCg0KIyMgRmFtaWx5IFg5NTA0NzcNCg0KIVtYOTUwNDc3XShYOTUwNDc3LnBuZykNCg0KV2hpY2ggc3BlY2llcyBhcmUgaW4gdGhlIGZhbWlseSBYOTUwNDc3Pw0KYGBge3J9DQptYXNzdF9yZXN1bHRzICU+JSANCiAgZmlsdGVyKEZBTUlMWV9JRCA9PSAiWDk1MDQ3NyIpICU+JSANCiAgc2VsZWN0KE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShOQU1FLCBGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTUwNDc3IikgJT4lIA0KICBjb3VudChOQU1FKQ0KYGBgDQoNCldoaWNoIGZlYXR1cmVzIHdpdGggTUFTU1QgaGl0cyBhcmUgaW4gdGhlIGZhbWlseSBYOTUwNDc3Pw0KYGBge3J9DQptYXNzdF9yZXN1bHRzICU+JSANCiAgZmlsdGVyKEZBTUlMWV9JRCA9PSAiWDk1MDQ3NyIpICU+JSANCiAgc2VsZWN0KEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgTkFNRSwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShGQU1JTFlfSUQsIEZFQVRVUkVfSUQsIE5BTUUpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTUwNDc3IikgJT4lIA0KICBjb3VudChGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQpgYGANCg0KIyMgRmFtaWx5IFg5NzAwMzQNCg0KIVtYOTcwMDM0XShYOTcwMDM0LnBuZykNCg0KV2hpY2ggc3BlY2llcyBhcmUgaW4gdGhlIGZhbWlseSBYOTcwMDM0Pw0KYGBge3J9DQptYXNzdF9yZXN1bHRzICU+JSANCiAgZmlsdGVyKEZBTUlMWV9JRCA9PSAiWDk3MDAzNCIpICU+JSANCiAgc2VsZWN0KE5BTUUsIEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShOQU1FLCBGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTcwMDM0IikgJT4lIA0KICBjb3VudChOQU1FKQ0KYGBgDQoNCldoaWNoIGZlYXR1cmVzIHdpdGggTUFTU1QgaGl0cyBhcmUgaW4gdGhlIGZhbWlseSBYOTcwMDM0Pw0KYGBge3J9DQptYXNzdF9yZXN1bHRzICU+JSANCiAgZmlsdGVyKEZBTUlMWV9JRCA9PSAiWDk3MDAzNCIpICU+JSANCiAgc2VsZWN0KEZBTUlMWV9JRCwgRkVBVFVSRV9JRCwgTkFNRSwgcC52YWx1ZS5mZHIpICU+JSANCiAgYXJyYW5nZShGQU1JTFlfSUQsIEZFQVRVUkVfSUQsIE5BTUUpDQoNCm1hc3N0X3Jlc3VsdHMgJT4lIA0KICBmaWx0ZXIoRkFNSUxZX0lEID09ICJYOTcwMDM0IikgJT4lIA0KICBjb3VudChGQU1JTFlfSUQsIEZFQVRVUkVfSUQpDQpgYGANCg0KIyBTZXNzaW9uIGluZm8NCg0KYGBge3J9DQpzZXNzaW9uSW5mbygpDQpgYGANCg==